package junior.db.dao.update;

import junior.db.dao.base.AbstractFilter;
import junior.db.dao.base.FilterSqlHelper;
import junior.db.dao.base.MybatisHelper;
import junior.db.dao.base.SqlValue;
import junior.db.dao.base.IEntityWrapper;
import junior.db.dao.base.INamespaceAware;
import junior.util.value.ValueUtils;

import java.util.List;
import java.util.Optional;
import java.util.function.BiFunction;

/**
 * @author wubo
 * @date 2024/9/3 14:44
 */
class UpdateSqlHelper extends FilterSqlHelper {
    
    static String insert(Object entity, InsertOption option, BiFunction<String, Object, String> onValueFunc) {
        setEntity(option, entity);
        option.beforeExecuteNode();
        StringBuilder buff = new StringBuilder();
        buff.append("insert into ").append(option.getTable());
        buff.append("(");
        String[] insertFields = option.getInsertFields();
        int fieldIndex = 0;
        for (int i = 0; i < insertFields.length; i++) {
            if (fieldIndex++ > 0) {
                buff.append(", ");
            }
            buff.append(option.getColumn(insertFields[i]));
        }
        buff.append(")");
        buff.append(" values (");
        Object[] values = option.getInsertValues();
        int valueIndex = 0;
        for (int i = 0; i < values.length; i++) {
            if (valueIndex++ > 0) {
                buff.append(", ");
            }
            Object value = values[i];
            if (value instanceof SqlValue) {
                SqlValue sqlValue = (SqlValue) value;
                for (int j = 0; j < sqlValue.getList().size(); j++) {
                    SqlValue.Part part = sqlValue.getList().get(j);
                    if (part.isArg()) {
                        Object val = part.getVal();
                        String path = "option.insertValues[" + i + "].list[" + j + "].val";
                        buff.append(onValueFunc.apply(path, val));
                    } else {
                        buff.append(part.getVal());
                    }
                }
            } else {
                String path = "option.insertValues[" + i + "]";
                buff.append(onValueFunc.apply(path, value));
            }
        }
        buff.append(")");
        return buff.toString();
    }
    
    static String batchInsert(List entities, BatchInsertOption option, BiFunction<String, Object, String> onValueFunc) {
        setEntity(option, entities.toArray());
        option.beforeExecuteNode();
        StringBuilder buff = new StringBuilder();
        buff.append("insert into ").append(option.getTable());
        buff.append("(");
        String[] insertFields = option.getInsertFields();
        int fieldIndex = 0;
        for (int i = 0; i < insertFields.length; i++) {
            if (fieldIndex++ > 0) {
                buff.append(", ");
            }
            buff.append(option.getColumn(insertFields[i]));
        }
        buff.append(")");
        buff.append(" values ");
        List<Object[]> insertValues = option.getInsertValues();
        int insertIndex = 0;
        for (Object[] values : insertValues) {
            if (insertIndex > 0) {
                buff.append(", ");
            }
            buff.append("(");
            int valueIndex = 0;
            for (int i = 0; i < values.length; i++) {
                if (valueIndex++ > 0) {
                    buff.append(", ");
                }
                Object value = values[i];
                if (value instanceof SqlValue) {
                    SqlValue sqlValue = (SqlValue) value;
                    for (int j = 0; j < sqlValue.getList().size(); j++) {
                        SqlValue.Part part = sqlValue.getList().get(j);
                        if (part.isArg()) {
                            Object val = part.getVal();
                            String path = "option.insertValues[" + insertIndex + "].[" + i + "].list[" + j + "].val";
                            buff.append(onValueFunc.apply(path, val));
                        } else {
                            buff.append(part.getVal());
                        }
                    }
                } else {
                    String path = "option.insertValues[" + insertIndex + "].[" + i + "]";
                    buff.append(onValueFunc.apply(path, value));
                }
            }
            buff.append(")");
            insertIndex++;
        }
        return buff.toString();
    }
    
    static String upsert(Object entity, UpsertOption option, BiFunction<String, Object, String> onValueFunc) {
        setEntity(option, entity);
        option.beforeExecuteNode();
        StringBuilder buff = new StringBuilder();
        buff.append("insert into ").append(option.getTable());
        buff.append("(");
        String[] insertFields = option.getInsertFields();
        int fieldIndex = 0;
        for (int i = 0; i < insertFields.length; i++) {
            if (fieldIndex++ > 0) {
                buff.append(", ");
            }
            buff.append(option.getColumn(insertFields[i]));
        }
        buff.append(")");
        buff.append(" values (");
        Object[] values = option.getInsertValues();
        int valueIndex = 0;
        for (int i = 0; i < values.length; i++) {
            if (valueIndex++ > 0) {
                buff.append(", ");
            }
            Object value = values[i];
            if (value instanceof SqlValue) {
                SqlValue sqlValue = (SqlValue) value;
                for (int j = 0; j < sqlValue.getList().size(); j++) {
                    SqlValue.Part part = sqlValue.getList().get(j);
                    if (part.isArg()) {
                        Object val = part.getVal();
                        String path = "option.insertValues[" + i + "].list[" + j + "].val";
                        buff.append(onValueFunc.apply(path, val));
                    } else {
                        buff.append(part.getVal());
                    }
                }
            } else {
                String path = "option.insertValues[" + i + "]";
                buff.append(onValueFunc.apply(path, value));
            }
        }
        buff.append(")");
        if (option.getUpdateFields().length > 0) {
            buff.append(" on duplicate key update ");
            for (int i = 0; i < option.getUpdateFields().length; i++) {
                if (i > 0) {
                    buff.append(", ");
                }
                buff.append(option.getColumn(option.getUpdateFields()[i])).append(" = ");
                Object value = option.getUpdateValues()[i];
                if (value instanceof SqlValue) {
                    SqlValue sqlValue = (SqlValue) value;
                    for (int j = 0; j < sqlValue.getList().size(); j++) {
                        SqlValue.Part part = sqlValue.getList().get(j);
                        if (part.isArg()) {
                            Object val = part.getVal();
                            String path = "option.updateValues[" + i + "].list[" + j + "].val";
                            buff.append(onValueFunc.apply(path, val));
                        } else {
                            buff.append(part.getVal());
                        }
                    }
                } else {
                    String path = "option.updateValues[" + i + "]";
                    buff.append(onValueFunc.apply(path, value));
                }
            }
        }
        return buff.toString();
    }
    
    static String batchUpsert(List entities, BatchUpsertOption option, BiFunction<String, Object, String> onValueFunc) {
        setEntity(option, entities.toArray());
        option.beforeExecuteNode();
        StringBuilder buff = new StringBuilder();
        buff.append("insert into ").append(option.getTable());
        buff.append("(");
        String[] insertFields = option.getInsertFields();
        int fieldIndex = 0;
        for (int i = 0; i < insertFields.length; i++) {
            if (fieldIndex++ > 0) {
                buff.append(", ");
            }
            buff.append(option.getColumn(insertFields[i]));
        }
        buff.append(")");
        buff.append(" values ");
        List<Object[]> insertValues = option.getInsertValues();
        int insertIndex = 0;
        for (Object[] values : insertValues) {
            if (insertIndex > 0) {
                buff.append(", ");
            }
            buff.append("(");
            int valueIndex = 0;
            for (int i = 0; i < values.length; i++) {
                if (valueIndex++ > 0) {
                    buff.append(", ");
                }
                Object value = values[i];
                if (value instanceof SqlValue) {
                    SqlValue sqlValue = (SqlValue) value;
                    for (int j = 0; j < sqlValue.getList().size(); j++) {
                        SqlValue.Part part = sqlValue.getList().get(j);
                        if (part.isArg()) {
                            Object val = part.getVal();
                            String path = "option.insertValues[" + insertIndex + "].[" + i + "].list[" + j + "].val";
                            buff.append(onValueFunc.apply(path, val));
                        } else {
                            buff.append(part.getVal());
                        }
                    }
                } else {
                    String path = "option.insertValues[" + insertIndex + "].[" + i + "]";
                    buff.append(onValueFunc.apply(path, value));
                }
            }
            buff.append(")");
            insertIndex++;
        }
        if (option.getUpdateFields().length > 0) {
            buff.append(" on duplicate key update ");
            for (int i = 0; i < option.getUpdateFields().length; i++) {
                if (i > 0) {
                    buff.append(", ");
                }
                String updateField = option.getUpdateFields()[i];
                buff.append(option.getColumn(updateField)).append(" = ");
                Object value = option.getUpdateValues()[i];
                if (value instanceof SqlValue) {
                    SqlValue sqlValue = (SqlValue) value;
                    for (int j = 0; j < sqlValue.getList().size(); j++) {
                        SqlValue.Part part = sqlValue.getList().get(j);
                        if (part.isArg()) {
                            Object val = part.getVal();
                            String path = "option.updateValues[" + i + "].list[" + j + "].val";
                            buff.append(onValueFunc.apply(path, val));
                        } else {
                            buff.append(part.getVal());
                        }
                    }
                } else if (value instanceof Optional && value == Optional.empty()) {
                    buff.append("values(" + updateField + ")");
                } else {
                    String path = "option.updateValues[" + i + "]";
                    buff.append(onValueFunc.apply(path, value));
                }
            }
        }
        return buff.toString();
    }
    
    static String update(Object entity, UpdateOption option, BiFunction<String, Object, String> onValueFunc) {
        setEntity(option, entity);
        UpdateOptionHelper.setVersion(option, entity);
        option.beforeExecuteNode();
        StringBuilder buff = new StringBuilder();
        buff.append("update ").append(option.getTable());
        buff.append(" set ");
        int updateIndex = 0;
        for (Object value : option.getUpdateValues()) {
            String field = option.getUpdateFields()[updateIndex];
            if (value == null && option.isSkipNull()) {
                List<String> skipNullExceptFields = option.getSkipNullExceptFields();
                if (skipNullExceptFields == null || !skipNullExceptFields.contains(field)) {
                    continue;
                }
            }
            if (updateIndex > 0) {
                buff.append(", ");
            }
            buff.append(option.getColumn(field)).append(" = ");
            if (value instanceof SqlValue) {
                SqlValue sqlValue = (SqlValue) value;
                for (int j = 0; j < sqlValue.getList().size(); j++) {
                    SqlValue.Part part = sqlValue.getList().get(j);
                    if (part.isArg()) {
                        Object val = part.getVal();
                        String path = "option.updateValues[" + updateIndex + "].list[" + j + "].val";
                        buff.append(onValueFunc.apply(path, val));
                    } else {
                        buff.append(part.getVal());
                    }
                }
            } else {
                String path = "option.updateValues[" + updateIndex + "]";
                buff.append(onValueFunc.apply(path, value));
            }
            updateIndex++;
        }
        buff.append(getFilters(option, WHERE, "option", onValueFunc));
        return buff.toString();
    }
    
    static String batchUpdate(List entities, BatchUpdateOption option, BiFunction<String, Object, String> onValueFunc) {
        setEntity(option, entities.toArray());
        UpdateOptionHelper.batchSetVersion(option, entities.get(0).getClass());
        option.beforeExecuteNode();
        StringBuilder buff = new StringBuilder();
        for (int entityIndex = 0; entityIndex < entities.size(); entityIndex++) {
            if (entityIndex > 0) {
                buff.append(";\n");
            }
            buff.append("update ").append(option.getTable());
            buff.append(" set ");
            int updateIndex = 0;
            Object[] values = option.getUpdateValues().get(entityIndex);
            for (Object value : values) {
                String field = option.getUpdateFields()[updateIndex];
                if (value == null && option.isSkipNull()) {
                    List<String> skipNullExceptFields = option.getSkipNullExceptFields();
                    if (skipNullExceptFields == null || !skipNullExceptFields.contains(field)) {
                        continue;
                    }
                }
                if (updateIndex > 0) {
                    buff.append(", ");
                }
                buff.append(option.getColumn(field)).append(" = ");
                if (value instanceof SqlValue) {
                    SqlValue sqlValue = (SqlValue) value;
                    for (int j = 0; j < sqlValue.getList().size(); j++) {
                        SqlValue.Part part = sqlValue.getList().get(j);
                        if (part.isArg()) {
                            Object val = part.getVal();
                            String path = "option.updateValues[" + entityIndex + "].[" + updateIndex + "].list[" + j + "].val";
                            buff.append(onValueFunc.apply(path, val));
                        } else {
                            buff.append(part.getVal());
                        }
                    }
                } else {
                    String path = "option.updateValues[" + entityIndex + "].[" + updateIndex + "]";
                    buff.append(onValueFunc.apply(path, value));
                }
                updateIndex++;
            }
            buff.append(getFilters(option.getEntityFilters().get(entityIndex), WHERE, "option.entityFilters[" + entityIndex + "]", onValueFunc));
        }
        return buff.toString();
    }
    
    static String updateField(UpdateFieldOption option, String path, BiFunction<String, Object, String> onValueFunc) {
        option.beforeExecuteNode();
        StringBuilder buff = new StringBuilder();
        buff.append("update ").append(option.getTable());
        buff.append(" set ");
        int updateIndex = 0;
        for (Object value : option.getUpdateValues()) {
            String field = option.getUpdateFields()[updateIndex];
            if (value == null && option.isSkipNull()) {
                List<String> skipNullExceptFields = option.getSkipNullExceptFields();
                if (skipNullExceptFields == null || !skipNullExceptFields.contains(field)) {
                    continue;
                }
            }
            if (updateIndex > 0) {
                buff.append(", ");
            }
            buff.append(option.getColumn(field)).append(" = ");
            if (value instanceof SqlValue) {
                SqlValue sqlValue = (SqlValue) value;
                for (int j = 0; j < sqlValue.getList().size(); j++) {
                    SqlValue.Part part = sqlValue.getList().get(j);
                    if (part.isArg()) {
                        Object val = part.getVal();
                        String valuePath = getPath(path) + "updateValues[" + updateIndex + "].list[" + j + "].val";
                        buff.append(onValueFunc.apply(valuePath, val));
                    } else {
                        buff.append(part.getVal());
                    }
                }
            } else {
                String valuePath = getPath(path) + "updateValues[" + updateIndex + "]";
                buff.append(onValueFunc.apply(valuePath, value));
            }
            updateIndex++;
        }
        buff.append(getFilters(option, WHERE, path, onValueFunc));
        return buff.toString();
    }
    
    static String getUpdateFilters(AbstractFilter param, String path, BiFunction<String, Object, String> onValueFunc) {
        return FilterSqlHelper.getFilters(param, WHERE, path, onValueFunc);
    }
    
    static String delete(DeleteOption option, String path, BiFunction<String, Object, String> onValueFunc) {
        option.beforeExecuteNode();
        StringBuilder buff = new StringBuilder();
        if (option.getLogicDeleteField() != null) {
            buff.append("update ").append(option.getTable());
            buff.append(" set ").append(getColumn(option, option, option.getLogicDeleteField()));
            String valuePath = getPath(path) + "logicDeleteValue";
            buff.append(" = ").append(onValueFunc.apply(valuePath, option.getLogicDeleteValue()));
        } else {
            buff.append("delete from ").append(option.getTable());
        }
        buff.append(getFilters(option, WHERE, path, onValueFunc));
        return buff.toString();
    }
    
    private static String getColumn(INamespaceAware option, IEntityWrapper entityWrapper, String field) {
        return MybatisHelper.field2Column(option.getNamespace(), entityWrapper.getEntityClass(), field);
    }
    
    private static void setEntity(SingleInsertOption option, Object entity) {
        option.init(ValueUtils.ifNull(option.getEntityClass(), entity.getClass()), entity);
    }
    
    private static void setEntity(AbstractBatchInsertOption option, Object[] entities) {
        option.init(ValueUtils.ifNull(option.getEntityClass(), entities[0].getClass()), entities);
    }
    
    private static void setEntity(UpdateOption option, Object entity) {
        option.init(ValueUtils.ifNull(option.getEntityClass(), entity.getClass()), entity);
    }
    
    private static void setEntity(BatchUpdateOption option, Object[] entities) {
        option.init(ValueUtils.ifNull(option.getEntityClass(), entities[0].getClass()), entities);
    }
}
